package register

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/clevergo/websocket"
	"github.com/sirupsen/logrus"
	"github.com/valyala/fasthttp"
	"log"
	"strings"
	"sync"
	"time"
)

const (
	// Time allowed to write a message to the peer.
	writeWait = 10 * time.Second
	// Time allowed to read the next pong message from the peer.
	pongWait = 60 * time.Second
	//// Send pings to peer with this period. Must be less than pongWait.
	//pingPeriod = (pongWait * 9) / 10
	// Maximum message size allowed from peer.
	maxMessageSize = 1024 * 16
)

var (
	newline = []byte{'\n'}
	space   = []byte{' '}
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin:     func(ctx *fasthttp.RequestCtx) bool { return true },
}

type Client struct {
	conn         *websocket.Conn
	send         chan []byte
	closeCh      chan bool
	server       *Server
	uid          string
	lock         sync.RWMutex
	nodeAnnounce NodeAnnounce
	NodeStatus   *NodeStatus
	Handler
	register    bool
	ctx         *fasthttp.RequestCtx
	messageType int
}

type NodeStatus struct {
	Country  string
	Region   string
	Province string
	City     string
	ISP      string
	ConnNum   int      `json:"conn_num"`  //连接数
	Bandwidth float32  `json:"bandwidth"` //带宽Mbps
	Files     map[string]bool `json:"files"`     //缓存文件url
}

func AcquireClient() *Client {
	return clientPool.Get().(*Client)
}

func ReleaseClient(c *Client) {
	c.Reset()
	clientPool.Put(c)
}

var clientPool = &sync.Pool{
	New: func() interface{} {
		return &Client{nodeAnnounce: *AcquireAnnounce()}
	},
}

func (c *Client) Reset() {
	c.register = false
}

func (c *Client) SetServer(s *Server) {
	if c != nil {
		c.server = s
	}
}

func (c *Client) SetTime() {
	if c != nil {
		c.nodeAnnounce.Time = time.Now().Unix()
		c.nodeAnnounce.Timeout = 120
	}
}

func (c *Client) GetTime() int64 {
	if c != nil {
		return c.nodeAnnounce.Time
	}
	return 0
}

func (c *Client) SetConn(conn *websocket.Conn) {
	if c != nil {
		c.conn = conn
	}
}

func (c *Client) SetCtx(context *fasthttp.RequestCtx) {
	if c != nil {
		c.ctx = context
	}
}

func (c *Client) InitNodeStatus() {
	c.NodeStatus = &NodeStatus{
		Files: make(map[string]bool),
	}
	if c.ctx != nil {
		regionInfo, err := c.server.RegionDecoder.Region(c.RemoteAddr())
		if err != nil {
			logrus.Errorf("[Client.InitNodeStatus] regionDecoder.Region(%s) err: %s", c.RemoteAddr(), err.Error())
			return
		}
		c.NodeStatus.Country = regionInfo.Country
		c.NodeStatus.Region = regionInfo.Region
		c.NodeStatus.Province = regionInfo.Province
		c.NodeStatus.City = regionInfo.City
		c.NodeStatus.ISP = regionInfo.ISP
		err = c.server.RDB.Set("hello", "hi", 0).Err()
		if err != nil {
			logrus.Errorf("[Client.InitNodeStatus] RDB.Set err: %s", err.Error())
		}

	}
}

func (c *Client) readLoop() {
	//log.Printf("c.ctx.request.body:%v", c.ctx.Request.Body())

	c.conn.SetReadLimit(maxMessageSize)
	//c.conn.SetReadDeadline(time.Now().Add(pongWait))
	//c.conn.SetPongHandler(func(string) error {
	//	c.conn.SetReadDeadline(time.Now().Add(pongWait))
	//	return nil
	//})
	for {
		mt, message, err := c.conn.ReadMessage()
		if err != nil {
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
				log.Printf("error: %v", err)
				break
			}
			//TODO 删除 client map
			//c.conn.Close()
			//c.server.lock.Lock()
			c.write(websocket.CloseMessage, []byte{})
			c.conn.Close()
			//delete(c.server.clients, strings.ToLower(c.nodeAnnounce.PeerId))
			//c.server.lock.Unlock()
			break
		}
		message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
		c.messageType = mt
		go func() {
			c.processMsg(message)
		}()
	}
}

func (c *Client) write(mt int, payload []byte) (err error) {
	defer func() {
		switch p := recover(); p {
		case nil:
			// no panic
			// "expected" panic
		default:
			err = fmt.Errorf("write conn nil pointer")
		}
	}()
	err = c.conn.SetWriteDeadline(time.Now().Add(writeWait))
	if err != nil {
		log.Printf("[error] client write err:%v", err)
		return err
	}
	return c.conn.WriteMessage(mt, payload)
}

func (c *Client) sendMessage(msg []byte) error {
	//log.Printf("send message:%v", msg)
	if msg != nil && c != nil {
		return c.write(c.messageType, msg)
	}
	return nil
}

func (c *Client) SetId(id string) {
	if c.uid == "" && id != "" {
		c.uid = id
	}
}

func (c *Client) jsonResponse(value interface{}) error {
	b, err := json.Marshal(value)
	if err != nil {
		logrus.Errorf("[Client.jsonResponse] Marshal err: %s", err.Error())
		return err
	}
	if err := c.sendMessage(b); err != nil {
		logrus.Errorf("[Client.jsonResponse] sendMessage err: %s", err.Error())
		return err
	}
	return nil
}

func (c *Client) byteResponse(b []byte) {
	if err := c.sendMessage(b); err != nil {
		logrus.Errorf("[Client.jsonResponse] sendMessage err: %s", err.Error())
	}
}

func (c *Client) SetNodeAnnounce(announce *NodeAnnounce) {
	c.uid = string(announce.PeerId)
	//logrus.Debugf("[Hub.doRegister] %s", c.uid)
	if c.uid != "" {
		node := *announce
		client := *c
		//log.Printf("node annnounce:%v", client.nodeAnnounce)
		node.Time = time.Now().Unix()
		client.nodeAnnounce = node
		//log.Printf("node announce timeout:%v,time:%v", announce.Timeout, announce.Time)
		c.server.lock.Lock()
		c.server.clients[strings.ToLower(client.uid)] = client
		c.server.lock.Unlock()
		//if !(c.register) {
		localHost := c.server.localHost
		go func(a NodeAnnounce, host string) {
			c.register = true
			//log.Printf("announce register to station:%v", a.Time)
			const baseUrl = "register?node=%s&server=%s"
			fastClient := &fasthttp.Client{
				//ReadTimeout: 5*time.Second,
			}
			body, err := json.Marshal(node)
			if err != nil {
				log.Printf("[error]:set node announce json encode err:%v", err)
				return
			}
			//log.Printf("announce body:%v", string(body))
			req := fasthttp.AcquireRequest()
			req.Header.SetMethod("POST")
			//TODO client
			req.SetRequestURI(fmt.Sprintf(baseUrl, strings.ToLower(c.uid), localHost))
			req.SetHost(c.server.remoteHost)
			req.SetBody(body)
			resp := fasthttp.AcquireResponse()
			if err := fastClient.DoTimeout(req, resp, 10*time.Second); err != nil {
				log.Printf("[err]:(1)client send announce to transform server->%v", err)

			}
		}(node, localHost)
	}
}

func (c *Client) RemoteAddr() string {
	result := c.conn.RemoteAddr().String()
	if strings.Contains(result, ":") {
		result = result[:strings.Index(result, ":")]
	}
	return result
}

func (c *Client) processMsg(message []byte) {
	//logrus.Info("[Client.handle] %s", string(message))
	action := struct {
		Action   string `json:"action"`
		ToPeerId string `json:"to_peer_id"`
	}{}

	if err := json.Unmarshal(message, &action); err != nil {
		// TODO 错误是否告知客戶端
		logrus.Info("[Client.handle] json.Unmarshal %s", err.Error())
		return
	}
	handler := c.getHandler(action.Action, action.ToPeerId)
	if handler != nil {
		handler.Handle(message)
	}
}

func (c *Client) getHandler(action, toPeerId string) Handler {
	//log.Printf("[Client.CreateHandler] action:%s, toPeerId: %s", action, toPeerId)
	switch {
	case strings.Contains(action, "announce"):
		return &announceHandler{c}
	case strings.Contains(action, "status"):
		return &StatusHandler{c}
	case strings.Contains(action, "update"):
		return &UpdateHandler{c}
	case strings.Contains(action, "get"):
		log.Printf("[getHandler] action get from ip: %s", c.RemoteAddr())
		return &GetHandler{
			client:        c,
			remoteAddr: c.RemoteAddr(),
			regionDecoder: c.server.RegionDecoder,
		}
	case strings.Contains(action, "answer"), strings.Contains(action, "candidate"):
		return &ForwardHandler{client: c, toPeerId: toPeerId}

	case strings.Contains(action, "pull"):
		return &PullHandler{client: c}
	default:
		log.Printf("[waring]:get handler find no case")
		return nil
	}
}

func ServeWs(server *Server, ctx *fasthttp.RequestCtx) {
	log.Printf("[drager]: begin upgrade")
	err := upgrader.Upgrade(ctx, func(conn *websocket.Conn) {
		//client := AcquireClient()
		log.Printf("[drager]: in upgrade callback")
		client := Client{nodeAnnounce: NodeAnnounce{}}
		client.SetCtx(ctx)
		client.SetConn(conn)
		client.SetServer(server)
		client.InitNodeStatus()
		client.Reset()
		client.SetTime()
		client.readLoop()
		server.lock.Lock()
		delete(server.clients, strings.ToLower(client.uid))
		server.lock.Unlock()
		log.Printf("release client")
		//ReleaseClient(client)
	})
	if err != nil {
		log.Printf("[err]:serveWs handles websocket requests->%v", err)
		return
	}
}

func ServerPush(server *Server, ctx *fasthttp.RequestCtx) {
	to := ctx.QueryArgs().Peek("to")
	fmt.Printf("Push! %s\n", string(to))
	//log.Printf("Push body:%v", string(ctx.Request.Body()))
	//fmt.Printf("Push remotehost %s\n", server.remoteHost)
	server.lock.Lock()
	client, ok := server.clients[strings.ToLower(string(to))]
	server.lock.Unlock()
	if ok {
		client.sendMessage(ctx.Request.Body())
	} else {
		log.Printf("[err]:push message ServerPush can't find client:->%v", client)

	}

}


